import java.awt.Button;
import java.awt.Checkbox;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.gui.GUI;
import ij.gui.Plot;
import ij.gui.Roi;
import ij.measure.Measurements;
import ij.plugin.frame.PlugInFrame;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;

public class Classification_ extends PlugInFrame implements ActionListener {

	public static final String DO_COMMAND = "Classify";

	public static final String DIAGNOSIS_FEATURE = "Diagnosis";
	public static final String MIN_FEATURE = "Minimum";
	public static final String MAX_FEATURE = "Maximum";
	public static final String MEAN_FEATURE = "Mean";
	public static final String DEVIATION_FEATURE = "Standard Deviation";
	public static final String KURTOSIS_FEATURE = "Kurtosis";
	public static final String SKEWNESS_FEATURE = "Skewness";
	public static final String ECHOGENICITY_FEATURE = "Echogenicity";
	public static final String CORRELATION_FEATURE = "Correlation";
	public static final String SPECTRUM_FEATURE = "Spectrum";
	public static final String DIFFERENTIATION_FEATURE = "Differentiation";
	public static final String HOMOGENITY_FEATURE = "Homogeneity";
	
	
	public static final double min_min = 0.0;
	public static final double min_max = 108.0;
	public static final double min_mean = 36.661920;
	public static final double min_stddev = 14.862094;
	public static final double min_echo = 24.84;
	public static final double min_kurt = -0.634327;
	public static final double min_skew = 0.082453;
	public static final double min_corr = 0.686936;
	public static final double min_spec = 5.0;
	
	public static final double max_min = 71.0;
	public static final double max_max = 215.0;
	public static final double max_mean = 120.544552;	
	public static final double max_stddev = 31.538651;
	public static final double max_echo = 47.53;
	public static final double max_kurt = 5.975105;
	public static final double max_skew = 1.774395;
	public static final double max_corr = 0.939987;
	public static final double max_spec = 13.0;
	

	public static final String[] FEATURES = { MIN_FEATURE, MAX_FEATURE,
			MEAN_FEATURE, DEVIATION_FEATURE, ECHOGENICITY_FEATURE,
			KURTOSIS_FEATURE, SKEWNESS_FEATURE, CORRELATION_FEATURE,
			SPECTRUM_FEATURE };
	public static boolean[] selected_features = new boolean[FEATURES.length];
	public static final String SAMPLE_FILENAME = "normsample.txt";

	public double min = 0;
	public double max = 255;

	private double mean = Double.NaN;
	private double deviation = Double.NaN;
	private double kurtosis = Double.NaN;
	private double skewness = Double.NaN;
	private double echogenicity = Double.NaN;
	private double corr = Double.NaN;
	private double spec = Double.NaN;

	private ImagePlus imp;
	private ImageProcessor ip;
	private Panel mainPane;

	private Map<String, TextField> commandFields = new TreeMap<String, TextField>();
	private Map<String, Label> results = new TreeMap<String, Label>();
	private Map<String, TextField> inputs = new TreeMap<String, TextField>();
	private Map<String, Checkbox> checkboxes = new TreeMap<String, Checkbox>();
	private LinkedList<Double[]> sample = new LinkedList<Double[]>();
	
	public Classification_() {
		super("Classification");
		imp = WindowManager.getCurrentImage();
		if (imp == null) {
			IJ.showMessage("No image", "There are no images open.");
			return;
		}
		try {
			StreamTokenizer in = new StreamTokenizer(new FileReader(SAMPLE_FILENAME));
			int token = in.nextToken();
			while (token != StreamTokenizer.TT_EOF) {
				Double[] item = new Double[FEATURES.length + 1];
				for (int i = 0; i < FEATURES.length + 1; ++i) {
					item[i] = in.nval;
					token = in.nextToken();
				}
				sample.add(item);
			}
		} catch (FileNotFoundException e){
			IJ.showMessage("No sample file", "Sample file is not found.");
			return;
		} catch (IOException e){
		}
		mainPane = new Panel();

		mainPane.setLayout(new GridLayout(0, 2, 10, 5));
		for (int i = 0; i < FEATURES.length; ++i) {
			addCheckbox(FEATURES[i]);
	
		}
		addResult(DIAGNOSIS_FEATURE);
		addResult(ECHOGENICITY_FEATURE);
		addCommand(DO_COMMAND);
		add(mainPane);
		pack();
		setVisible(true);
		GUI.center(this);
	}

	public void run(java.lang.String arg) {
		if (imp == null) {
			return;
		}
		ip = imp.getProcessor();
		Roi roi = imp.getRoi();
	}

	private void addCheckbox(String name) {
		Checkbox cb = new Checkbox(name, false);
		checkboxes.put(name, cb);
		mainPane.add(cb);
		addLabel("");
	}

	private void addCommand(String labelText, String buttonText,
			double defaultValue) {
		Label label = new Label(labelText);
		TextField textField = new TextField(String.format("%.0f", defaultValue));
		commandFields.put(buttonText, textField);
		inputs.put(labelText, textField);
		Button button = new Button(buttonText);
		button.addActionListener(this);
		button.setActionCommand(buttonText);
		mainPane.add(label);
		mainPane.add(textField);
		mainPane.add(button);
	}

	private void addNonEditableInput(String labelText, double defaultValue) {
		Label label = new Label(labelText);
		TextField textField = new TextField(String.format("%.0f", defaultValue));
		textField.setEditable(false);
		inputs.put(labelText, textField);
		mainPane.add(label);
		mainPane.add(textField);
		addLabel("");
	}

	private void addResult(String resultName) {
		Label nameLabel = new Label(resultName + ":");
		Label resultLabel = new Label();
		results.put(resultName, resultLabel);
		mainPane.add(nameLabel);
		mainPane.add(resultLabel);
	}

	private void addCommand(String buttonText) {
		Button button = new Button(buttonText);
		button.addActionListener(this);
		button.setActionCommand(buttonText);
		mainPane.add(button);
		addLabel("");
	}

	private void addLabel(String labelText) {
		mainPane.add(new Label(labelText));
	}
	
	
	private double norm(Double[] x, Double[] y){
		double res = 0.0;
		for (int i = 0; i < selected_features.length; i++) {
			if (selected_features[i])
				res += (x[i] - y[i]) * (x[i] - y[i]);
		}
		return Math.sqrt(res);
	}
	private <T> int index(T[] array, T x) {
		for (int i = 0; i < array.length; ++i)
			if (array[i].equals(x))
				return i;
		return -1;
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		imp = WindowManager.getCurrentImage();
		String command = e.getActionCommand();
		if (DO_COMMAND.equals(command)) {
			for (int i = 0; i < FEATURES.length; ++i)
				selected_features[i] = checkboxes.get(FEATURES[i]).getState();
			// FIND CURRENT PARAMETERS HERE
			Double[] currentVector = new Double[FEATURES.length + 1];
			corr = (calculateCorrelation(ip, 1, 0) + calculateCorrelation(ip, 0, 1) + calculateCorrelation(ip, -1, 0) + calculateCorrelation(ip, 0, -1)) / 4;
			calculate();
			drawRadialSpectrum(ip);
			currentVector[index(FEATURES, MIN_FEATURE)] = (min - min_min)/(max_min - min_min);
			currentVector[index(FEATURES, MAX_FEATURE)] = (max - min_max)/(max_max - min_max);
			currentVector[index(FEATURES, MEAN_FEATURE)] = (mean-min_mean)/(max_mean - min_mean);
			currentVector[index(FEATURES, DEVIATION_FEATURE)] = (deviation - min_stddev)/(max_stddev - min_stddev);
			currentVector[index(FEATURES, ECHOGENICITY_FEATURE)] = (echogenicity - min_echo)/(max_echo - min_echo);
			currentVector[index(FEATURES, KURTOSIS_FEATURE)] = (kurtosis - min_kurt)/(max_kurt - min_kurt);
			currentVector[index(FEATURES, SKEWNESS_FEATURE)] = (skewness - min_skew)/(max_skew - min_skew);
			currentVector[index(FEATURES, CORRELATION_FEATURE)] = (corr - min_corr)/(max_corr - min_corr);
			currentVector[index(FEATURES, SPECTRUM_FEATURE)] = (spec - min_spec)/(max_spec - min_spec);
			double min = Double.MAX_VALUE;
			double result = -1.0;
			for (Iterator<Double[]> iterator = sample.iterator(); iterator.hasNext();) {
				Double[] item = (Double[]) iterator.next();
				if (norm(item, currentVector) < min) {
					result = item[FEATURES.length];
					min = norm(item, currentVector);
				}
			}
			results.get(DIAGNOSIS_FEATURE).setText((result == 0.0) ? "Patology" : "Norm");
			results.get(ECHOGENICITY_FEATURE).setText((echogenicity > 35.0) ? "Increased":"Norm");
		}
	}
	

	private void drawRadialSpectrum(ImageProcessor ip) {
		imp = WindowManager.getCurrentImage();
		if (imp == null) {
			IJ.showMessage("No image", "There are no images open.");
			return;
		}
		int width = ip.getWidth();
		double[][] results = new double[width / 2][width / 2];
		for (int y = 0; y < width / 2; y++)
			for (int x = 0; x < width / 2; x++) {
					results[x][y] += ip.getPixelValue(width / 2 + x, width / 2 + y);
					results[x][y] += ip.getPixelValue(width / 2 - x - 1, width / 2 + y);
					results[x][y] += ip.getPixelValue(width / 2 + x, width / 2 - y - 1);
					results[x][y] += ip.getPixelValue(width / 2 - x - 1, width / 2 - y - 1);
					results[x][y] /= 4.0;
				}
		
		for (int y = 0; y < width / 2; y++){
			for (int x = 0; x < width / 2; x++) {
				results[0][y] = 0.0;
				results[x][0] = 0.0;
			}
		}
		
		double[] keys = new double[width / 2];
		double[] values = new double[width / 2];
		double[] counts = new double[width / 2];
		values[0] = 0.0;
		counts[0] = 1.0;
		for (int r = 0; r < width / 2; ++r){
			keys[r] = r;
			for (int loci = 0; loci < width / 2; ++loci)
				for (int locj = 0; locj < width / 2; ++locj) {
					int position = loci * loci + locj * locj;
					if (position <= r * r && position > (r - 1) * (r - 1)) {
						values[r] += results[loci][locj];
						counts[r] += 1.0;
					}
				}
		}
		double maxV = 0.0;
		for (int r = 0; r < width / 2; ++r){
			values[r] /= counts[r];
			if (values[r] > maxV){
				maxV = values[r];
				spec = keys[r];
			}
			
		}
	}
	
	public double calculateCorrelation(ImageProcessor ip, int n, int m) {
		byte[] mask = ip.getMaskArray();
		int width = ip.getWidth();
		int height = ip.getHeight();
		Rectangle roi = ip.getRoi();
		int rx, ry, rw, rh;
		if (roi != null) {
			rx = roi.x;
			ry = roi.y;
			rw = roi.width;
			rh = roi.height;
		} else {
			rx = 0;
			ry = 0;
			rw = width;
			rh = height;
		}
		int pixelCount = 0;
		int i, mi;
		double v, v2, sum1 = 0.0, sum2 = 0.0;
		for (int y = ry, my = 0; y < (ry + rh); y++, my++) {
			i = y * width + rx;
			mi = my * rw;
			for (int x = rx; x < (rx + rw); x++) {
				if (mask == null || mask[mi++] != 0) {
					pixelCount++;
					v = ip.getPixelValue(x, y);
					v2 = v * v;
					sum1 += v;
					sum2 += v2;
				}
				i++;
			}
		}
		double variance = sum2 / (double)pixelCount - sum1 * sum1 / (double)pixelCount / (double)pixelCount;
		if (variance > 0.0) {
			int consideredPixelCount = 0;
			double avg = sum1 / pixelCount;
			double result = 0.0;
			for (int y = ry, my = 0; y < (ry + rh); y++, my++) {
				i = y * width + rx;
				mi = my * rw;
				for (int x = rx; x < (rx + rw); x++)
					if (mask == null || mask[mi++] != 0)
						if (x + n < rx + rw && x + n >= rx && y + m < ry + rh && y + m >= ry
								&& (mask == null || mask[mi - 1 + n + m * rw] != 0)) {
							result += (ip.getPixelValue(x, y) - avg) * (ip.getPixelValue(x + n, y + m) - avg);
							consideredPixelCount++;
						}
					i++;
			}
			return result / (double)consideredPixelCount / variance;
		} else
			return 0.0;
	}
	private void setMinMax() {
		ip.snapshot();
		ip.smooth();
		min = getMin();
		max = getMax();
		ip.reset();
	}

	private double getMax() {
		return imp.getStatistics().max;
	}

	private double getMin() {
		return imp.getStatistics().min;
	}

	private void calculate() {
		imp = WindowManager.getCurrentImage();
		if (imp == null) {
			IJ.showMessage("No image", "There are no images open.");
			return;
		}
		ip.snapshot();
		ip.smooth();
		max = imp.getStatistics().max;
		min = imp.getStatistics().min;
		mean = imp.getStatistics().mean;
		ip.reset();
		 
		ip.snapshot();
		ip.smooth();
		double realMax = getMax();
		double realMin = getMin();
		double fakeMin = (max * realMin - min * realMax) / (max - min);
		double fakeMax = fakeMin + 255.0 * (realMax - realMin) / (max - min);
		ip.setMinAndMax(fakeMin, fakeMax);

		ImageStatistics stat = imp.getStatistics(Measurements.MIN_MAX
				+ Measurements.STD_DEV + Measurements.KURTOSIS
				+ Measurements.SKEWNESS);
		mean = stat.mean;
		deviation = stat.stdDev;
		skewness = stat.skewness;
		kurtosis = stat.kurtosis;
		echogenicity = 100.0 * (mean - min) / (max - min);
		ip.reset();
	}

	private String format(double d) {
		return String.format("%f", d);
	}

	private String precisionFormat(double d, int precision) {
		return String.format("%." + precision + "f", d);
	}

}
